#define JMBC_INSIDE
#include "flFont.h"
#include <Math/mlMath.h>
#include <Core/clCore.h>
#include <GL/glu.h>

#define gop(i) glyph->outline.points[i]
#define goc(i) glyph->outline.contours[i]
#define got(i) glyph->outline.tags[i]
#define FTCT FT_CURVE_TAG
#define FTCT_ON FT_CURVE_TAG_ON
#define FTCT_CONIC FT_CURVE_TAG_CONIC
#define FTCT_CUBIC FT_CURVE_TAG_CUBIC

// Context

static FT_Library library;

#define STORE ID_SMALL_STORE

static FLFont *store[STORE];

void
flFontCreateContext(void)
{
	FT_Init_FreeType(&library);

	idClearStore(FLFont, STORE, store);
}

void
flFontDestroyContext(void)
{
	FT_Done_FreeType(library);
}

// Generation / Deletion

static
void
Init(FLFont *font)
{
}

static
void
Term(FLFont *font)
{
}

void
flGenFonts(unsigned int n, unsigned int *fonts)
{
	idGenObjects(FLFont, STORE, store, Init, n, fonts);
}

void
flDeleteFonts(unsigned int n, unsigned int *fonts)
{
	idDeleteObjects(store, Term, n, fonts);
}

// Load / Unload

void
flFontLoad(unsigned int id, unsigned int filepath)
{
	FLFont *font;
	FT_Error error;
	char tmp[256];

	font = store[id];

	wcstombs(tmp, tlStringData(filepath), 256);
	error = FT_New_Face(library, tmp, 0, &font->face);

	if(error == FT_Err_Unknown_File_Format || error)
		elErrorSet(EL_ERROR_INVALID);
}

void
flFontUnload(unsigned int id)
{
	FLFont *font;

	font = store[id];

	FT_Done_Face(font->face);
}

// Query

void
flFontStringMetrics(unsigned int id, unsigned int string, unsigned int *w, unsigned int *h)
{
	FLFont *font;
	const wchar_t *c;

	font = store[id];

	*w = *h = 0;
	for(c = tlStringData(string); *c; c++)
	{
		FT_Load_Char(font->face, *c, FT_LOAD_DEFAULT);
		*w += font->face->glyph->advance.x / 64;
		*h = (unsigned int) mlMax((float) *h, (float) font->face->glyph->metrics.height / 64);
	}
}

// Operation

static
void
flConicBezier(const FT_Vector *p0, const FT_Vector *p1, const FT_Vector *p2, float t, double *v) 
{
	v[0] = (1.0f - t) * (1.0f - t) * p0->x / 64.0f  + 2.0f * (1.0f - t) * t * p1->x / 64.0f  + t * t * p2->x / 64.0f;
	v[1] = (1.0f - t) * (1.0f - t) * p0->y / 64.0f  + 2.0f * (1.0f - t) * t * p1->y / 64.0f  + t * t * p2->y / 64.0f;
}

static
void
flCubicBezier(const FT_Vector *p0, const FT_Vector *p1, const FT_Vector *p2, const FT_Vector *p3, float t, double *v) 
{
	v[0] = (1.0f - t) * (1.0f - t) *(1.0f - t) * p0->x / 64.0f  + 3.0f * (1.0f - t) * (1.0f - t) * t * p1->x / 64.0f  + 3.0f * (1.0f - t) * t * t * p2->x / 64.0f  + t * t * t * p3->x / 64.0f ;
	v[1] = (1.0f - t) * (1.0f - t) *(1.0f - t) * p0->y / 64.0f  + 3.0f * (1.0f - t) * (1.0f - t) * t * p1->y / 64.0f  + 3.0f * (1.0f - t) * t * t * p2->y / 64.0f  + t * t * t * p3->y / 64.0f ;
}

void
flFontSetPixelSizes(unsigned int id, unsigned int w, unsigned int h)
{
	FLFont *font;

	font = store[id];

	FT_Set_Pixel_Sizes(font->face, w, h);
}

static
void
flTexGlyph2D(unsigned int w, unsigned int h, FT_GlyphSlot glyph)
{
	unsigned int i, j;
	GLubyte *pixels;

	pixels = malloc(2 * w * h * sizeof(GLubyte));
	memset(pixels, 0, 2 * w * h * sizeof(GLubyte));
	for(j = 0; j < (unsigned int) glyph->bitmap.rows; j++)
		for(i = 0; i < (unsigned int) glyph->bitmap.width; i++)
			pixels[2 * (j * w + i)] = pixels[2 * (j * w + i) + 1] = glyph->bitmap.buffer[(glyph->bitmap.rows - j - 1) * glyph->bitmap.pitch + i];

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, pixels);

	free(pixels);
}

static
void
flDrawTextureGlyph(FT_GlyphSlot glyph)
{
	unsigned int w, h;
	float tw, th;

	w = mlNextTwoPower((float) glyph->bitmap.width);
	h = mlNextTwoPower((float) glyph->bitmap.rows);

	flTexGlyph2D(w, h, glyph);

	tw = (float) glyph->bitmap.width / w;
	th = (float) glyph->bitmap.rows / h;

	w = glyph->metrics.width / 64;
	h = glyph->metrics.height / 64;

	glPushMatrix();
	glTranslatef((float) glyph->metrics.horiBearingX / 64, (float) - (glyph->metrics.height - glyph->metrics.horiBearingY) / 64, 0.0);
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0);
	glTexCoord2f(tw, 0.0f); glVertex2i(w, 0);
	glTexCoord2f(tw, th); glVertex2i(w, h);
	glTexCoord2f(0.0f, th); glVertex2i(0, h);
	glEnd();
	glPopMatrix();
}

static
unsigned int
flCountContourVertices(FT_GlyphSlot glyph, unsigned int ctour)
{
	unsigned int i, vrts;

	vrts = 0;
	for(i = (ctour ? goc(ctour - 1) + 1 : 0); i <= (unsigned int) goc(ctour); i++)
	{
		if((i == 0 || (ctour && i - (goc(ctour - 1) + 1) == 0)) && FTCT(got(i)) == FTCT_ON && FTCT(got(goc(ctour))) == FTCT_ON)
			vrts += 1;
		else if((i == 0 || (ctour && i - (goc(ctour - 1) + 1) == 0)) && FTCT(got(i)) == FTCT_ON && FTCT(got(goc(ctour))) == FTCT_CONIC && FTCT(got(goc(ctour) - 1)) == FTCT_ON)
			vrts += 3 + 1;
		else if((i == 0 || (ctour && i - (goc(ctour - 1) + 1) == 0)) && FTCT(got(i)) == FTCT_ON && FTCT(got(goc(ctour))) == FTCT_CONIC && FTCT(got(goc(ctour) - 1)) == FTCT_CONIC && FTCT(got(goc(ctour) - 2)) == FTCT_ON)
			vrts += 3 + 1 + 3 + 1;
		else if((i == 0 || (ctour && i - (goc(ctour - 1) + 1) == 0)) && FTCT(got(i)) == FTCT_ON && FTCT(got(goc(ctour))) == FTCT_CUBIC && FTCT(got(goc(ctour) - 1)) == FTCT_CUBIC && FTCT(got(goc(ctour) - 2)) == FTCT_ON)
			vrts += 4 + 1;
		else if((i >= 1 || (ctour && i - (goc(ctour - 1) + 1) >= 1)) && FTCT(got(i)) == FTCT_ON && FTCT(got(i - 1)) == FTCT_ON)
			vrts += 1;
		else if((i == 1 || (ctour && i - (goc(ctour - 1) + 1) == 1)) && FTCT(got(i)) == FTCT_ON && FTCT(got(i - 1)) == FTCT_CONIC && FTCT(got(goc(ctour))) == FTCT_ON)
			vrts += 3 + 1;
		else if((i == 1 || (ctour && i - (goc(ctour - 1) + 1) == 1)) && FTCT(got(i)) == FTCT_ON && FTCT(got(i - 1)) == FTCT_CONIC && FTCT(got(goc(ctour))) == FTCT_CONIC && FTCT(got(goc(ctour) - 1)) == FTCT_ON)
			vrts += 3 + 1 + 3 + 1;
		else if((i == 1 || (ctour && i - (goc(ctour - 1) + 1) == 1)) && FTCT(got(i)) == FTCT_ON && FTCT(got(i - 1)) == FTCT_CUBIC && FTCT(got(goc(ctour))) == FTCT_CUBIC && FTCT(got(goc(ctour) - 1)) == FTCT_ON)
			vrts += 4 + 1;
		else if((i >= 2 || (ctour && i - (goc(ctour - 1) + 1) >= 2)) && FTCT(got(i)) == FTCT_ON && FTCT(got(i - 1)) == FTCT_CONIC && FTCT(got(i - 2)) == FTCT_ON)
			vrts += 3 + 1;
		else if((i == 2 || (ctour && i - (goc(ctour - 1) + 1) == 2)) && FTCT(got(i)) == FTCT_ON && FTCT(got(i - 1)) == FTCT_CONIC && FTCT(got(i - 2)) == FTCT_CONIC && FTCT(got(goc(ctour))) == FTCT_ON)
			vrts += 3 + 1 + 3 + 1;
		else if((i == 2 || (ctour && i - (goc(ctour - 1) + 1) == 2)) && FTCT(got(i)) == FTCT_ON && FTCT(got(i - 1)) == FTCT_CUBIC && FTCT(got(i - 2)) == FTCT_CUBIC && FTCT(got(goc(ctour))) == FTCT_ON)
			vrts += 4 + 1;
		else if((i >= 3 || (ctour && i - (goc(ctour - 1) + 1) >= 3)) && FTCT(got(i)) == FTCT_ON && FTCT(got(i - 1)) == FTCT_CONIC && FTCT(got(i - 2)) == FTCT_CONIC && FTCT(got(i - 3)) == FTCT_ON)
			vrts += 3 + 1 + 3 + 1;
		else if((i >= 3 || (ctour && i - (goc(ctour - 1) + 1) >= 3)) && FTCT(got(i)) == FTCT_ON && FTCT(got(i - 1)) == FTCT_CUBIC && FTCT(got(i - 2)) == FTCT_CUBIC && FTCT(got(i - 3)) == FTCT_ON)
			vrts += 4 + 1;
	}
	return vrts;
}

void
flCreateContours(FT_GlyphSlot glyph, unsigned int **vrts, double ***ctours)
{
	unsigned int i, j, vrt;

	*vrts = malloc(glyph->outline.n_contours * sizeof(unsigned int));
	*ctours = malloc(glyph->outline.n_contours * sizeof(double *));
	for(j = 0; j < (unsigned int) glyph->outline.n_contours; j++)
	{
		(*vrts)[j] = flCountContourVertices(glyph, j);
		(*ctours)[j] = malloc(sizeof(double) * 3 * (*vrts)[j]);
		memset((*ctours)[j], 0, sizeof(double) * 3 * (*vrts)[j]);
		vrt = 0;
		for(i = (j ? goc(j - 1) + 1 : 0); i <= (unsigned int) goc(j); i++)
		{
			if((i == 0 || (j && i - (goc(j - 1) + 1) == 0)) && FTCT(got(i)) == FTCT_ON && FTCT(got(goc(j))) == FTCT_ON)
			{
				// vrt += 1;
				(*ctours)[j][vrt * 3] = (float) gop(i).x / 64; (*ctours)[j][vrt * 3 + 1] = (float) gop(i).y / 64; vrt += 1;
			}
			else if((i == 0 || (j && i - (goc(j - 1) + 1) == 0)) && FTCT(got(i)) == FTCT_ON && FTCT(got(goc(j))) == FTCT_CONIC && FTCT(got(goc(j) - 1)) == FTCT_ON)
			{
				// vrt += 3 + 1;
				flConicBezier(&gop(goc(j) - 1), &gop(goc(j)), &gop(i), 0.25f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&gop(goc(j) - 1), &gop(goc(j)), &gop(i), 0.50f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&gop(goc(j) - 1), &gop(goc(j)), &gop(i), 0.75f, &(*ctours)[j][vrt * 3]); vrt += 1;
				(*ctours)[j][vrt * 3] = (float) gop(i).x / 64; (*ctours)[j][vrt * 3 + 1] = (float) gop(i).y / 64; vrt += 1;
			}
			else if((i == 0 || (j && i - (goc(j - 1) + 1) == 0)) && FTCT(got(i)) == FTCT_ON && FTCT(got(goc(j))) == FTCT_CONIC && FTCT(got(goc(j) - 1)) == FTCT_CONIC && FTCT(got(goc(j) - 2)) == FTCT_ON)
			{
				// vrt += 3 + 1 + 3 + 1;
				FT_Vector mdl;

				mdl.x = (FT_Pos) (gop(goc(j) - 1).x + gop(goc(j)).x) / 2; mdl.y = (FT_Pos) (gop(goc(j) - 1).y + gop(goc(j)).y) / 2;
				flConicBezier(&gop(goc(j) - 2), &gop(goc(j) - 1), &mdl, 0.25f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&gop(goc(j) - 2), &gop(goc(j) - 1), &mdl, 0.50f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&gop(goc(j) - 2), &gop(goc(j) - 1), &mdl, 0.75f, &(*ctours)[j][vrt * 3]); vrt += 1;
				(*ctours)[j][vrt * 3] = (float) mdl.x / 64; (*ctours)[j][vrt * 3 + 1] = (float) mdl.y / 64; vrt += 1;
				flConicBezier(&mdl, &gop(goc(j)), &gop(i), 0.25f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&mdl, &gop(goc(j)), &gop(i), 0.50f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&mdl, &gop(goc(j)), &gop(i), 0.75f, &(*ctours)[j][vrt * 3]); vrt += 1;
				(*ctours)[j][vrt * 3] = (float) gop(i).x / 64; (*ctours)[j][vrt * 3 + 1] = (float) gop(i).y / 64; vrt += 1;
			}
			else if((i == 0 || (j && i - (goc(j - 1) + 1) == 0)) && FTCT(got(i)) == FTCT_ON && FTCT(got(goc(j))) == FTCT_CUBIC && FTCT(got(goc(j) - 1)) == FTCT_CUBIC && FTCT(got(goc(j) - 2)) == FTCT_ON)
			{
				// vrt += 4 + 1;
				flCubicBezier(&gop(goc(j) - 2), &gop(goc(j) - 1), &gop(goc(j)), &gop(i), 0.20f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flCubicBezier(&gop(goc(j) - 2), &gop(goc(j) - 1), &gop(goc(j)), &gop(i), 0.40f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flCubicBezier(&gop(goc(j) - 2), &gop(goc(j) - 1), &gop(goc(j)), &gop(i), 0.60f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flCubicBezier(&gop(goc(j) - 2), &gop(goc(j) - 1), &gop(goc(j)), &gop(i), 0.80f, &(*ctours)[j][vrt * 3]); vrt += 1;
				(*ctours)[j][vrt * 3] = (float) gop(i).x / 64; (*ctours)[j][vrt * 3 + 1] = (float) gop(i).y / 64; vrt += 1;
			}
			else if((i >= 1 || (j && i - (goc(j - 1) + 1) >= 1)) && FTCT(got(i)) == FTCT_ON && FTCT(got(i - 1)) == FTCT_ON)
			{
				//vrt += 1;
				(*ctours)[j][vrt * 3] = (float) gop(i).x / 64; (*ctours)[j][vrt * 3 + 1] = (float) gop(i).y / 64; vrt += 1;
			}
			else if((i == 1 || (j && i - (goc(j - 1) + 1) == 1)) && FTCT(got(i)) == FTCT_ON && FTCT(got(i - 1)) == FTCT_CONIC && FTCT(got(goc(j))) == FTCT_ON)
			{
				// vrt += 3 + 1;
				flConicBezier(&gop(goc(j)), &gop(i - 1), &gop(i), 0.25f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&gop(goc(j)), &gop(i - 1), &gop(i), 0.50f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&gop(goc(j)), &gop(i - 1), &gop(i), 0.75f, &(*ctours)[j][vrt * 3]); vrt += 1;
				(*ctours)[j][vrt * 3] = (float) gop(i).x / 64; (*ctours)[j][vrt * 3 + 1] = (float) gop(i).y / 64; vrt += 1;
			}
			else if((i == 1 || (j && i - (goc(j - 1) + 1) == 1)) && FTCT(got(i)) == FTCT_ON && FTCT(got(i - 1)) == FTCT_CONIC && FTCT(got(goc(j))) == FTCT_CONIC && FTCT(got(goc(j) - 1)) == FTCT_ON)
			{
				//vrt += 3 + 1 + 3 + 1;
				FT_Vector mdl;

				mdl.x = (FT_Pos) (gop(goc(j)).x + gop(i - 1).x) / 2; mdl.y = (FT_Pos) (gop(goc(j)).y + gop(i - 1).y) / 2;
				flConicBezier(&gop(goc(j) - 1), &gop(goc(j)), &mdl, 0.25f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&gop(goc(j) - 1), &gop(goc(j)), &mdl, 0.50f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&gop(goc(j) - 1), &gop(goc(j)), &mdl, 0.75f, &(*ctours)[j][vrt * 3]); vrt += 1;
				(*ctours)[j][vrt * 3] = (float) mdl.x / 64; (*ctours)[j][vrt * 3 + 1] = (float) mdl.y / 64; vrt += 1;
				flConicBezier(&mdl, &gop(i - 1), &gop(i), 0.25f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&mdl, &gop(i - 1), &gop(i), 0.50f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&mdl, &gop(i - 1), &gop(i), 0.75f, &(*ctours)[j][vrt * 3]); vrt += 1;
				(*ctours)[j][vrt * 3] = (float) gop(i).x / 64; (*ctours)[j][vrt * 3 + 1] = (float) gop(i).y / 64; vrt += 1;
			}
			else if((i == 1 || (j && i - (goc(j - 1) + 1) == 1)) && FTCT(got(i)) == FTCT_ON && FTCT(got(i - 1)) == FTCT_CUBIC && FTCT(got(goc(j))) == FTCT_CUBIC && FTCT(got(goc(j) - 1)) == FTCT_ON)
			{
				// vrt += 4 + 1;
				flCubicBezier(&gop(goc(j) - 1), &gop(goc(j)), &gop(i - 1), &gop(i), 0.20f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flCubicBezier(&gop(goc(j) - 1), &gop(goc(j)), &gop(i - 1), &gop(i), 0.40f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flCubicBezier(&gop(goc(j) - 1), &gop(goc(j)), &gop(i - 1), &gop(i), 0.60f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flCubicBezier(&gop(goc(j) - 1), &gop(goc(j)), &gop(i - 1), &gop(i), 0.80f, &(*ctours)[j][vrt * 3]); vrt += 1;
				(*ctours)[j][vrt * 3] = (float) gop(i).x / 64; (*ctours)[j][vrt * 3 + 1] = (float) gop(i).y / 64; vrt += 1;
			}
			else if((i >= 2 || (j && i - (goc(j - 1) + 1) >= 2)) && FTCT(got(i)) == FTCT_ON && FTCT(got(i - 1)) == FTCT_CONIC && FTCT(got(i - 2)) == FTCT_ON)
			{
				//vrt += 3 + 1;
				flConicBezier(&gop(i - 2), &gop(i - 1), &gop(i), 0.25f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&gop(i - 2), &gop(i - 1), &gop(i), 0.50f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&gop(i - 2), &gop(i - 1), &gop(i), 0.75f, &(*ctours)[j][vrt * 3]); vrt += 1;
				(*ctours)[j][vrt * 3] = (float) gop(i).x / 64; (*ctours)[j][vrt * 3 + 1] = (float) gop(i).y / 64; vrt += 1;
			}
			else if((i == 2 || (j && i - (goc(j - 1) + 1) == 2)) && FTCT(got(i)) == FTCT_ON && FTCT(got(i - 1)) == FTCT_CONIC && FTCT(got(i - 2)) == FTCT_CONIC && FTCT(got(goc(j))) == FTCT_ON)
			{
				//vrt += 3 + 1 + 3 + 1;
				FT_Vector mdl;

				mdl.x = (FT_Pos) (gop(i - 2).x + gop(i - 1).x) / 2; mdl.y = (FT_Pos) (gop(i - 2).y + gop(i - 1).y) / 2;
				flConicBezier(&gop(goc(j)), &gop(i - 2), &mdl, 0.25f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&gop(goc(j)), &gop(i - 2), &mdl, 0.50f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&gop(goc(j)), &gop(i - 2), &mdl, 0.75f, &(*ctours)[j][vrt * 3]); vrt += 1;
				(*ctours)[j][vrt * 3] = (float) mdl.x / 64; (*ctours)[j][vrt * 3 + 1] = (float) mdl.y / 64; vrt += 1;
				flConicBezier(&mdl, &gop(i - 1), &gop(i), 0.25f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&mdl, &gop(i - 1), &gop(i), 0.50f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&mdl, &gop(i - 1), &gop(i), 0.75f, &(*ctours)[j][vrt * 3]); vrt += 1;
				(*ctours)[j][vrt * 3] = (float) gop(i).x / 64; (*ctours)[j][vrt * 3 + 1] = (float) gop(i).y / 64; vrt += 1;
			}
			else if((i == 2 || (j && i - (goc(j - 1) + 1) == 2)) && FTCT(got(i)) == FTCT_ON && FTCT(got(i - 1)) == FTCT_CUBIC && FTCT(got(i - 2)) == FTCT_CUBIC && FTCT(got(goc(j))) == FTCT_ON)
			{
				//vrt += 4 + 1;
				flCubicBezier(&gop(goc(j)), &gop(i - 2), &gop(i - 1), &gop(i), 0.20f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flCubicBezier(&gop(goc(j)), &gop(i - 2), &gop(i - 1), &gop(i), 0.40f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flCubicBezier(&gop(goc(j)), &gop(i - 2), &gop(i - 1), &gop(i), 0.60f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flCubicBezier(&gop(goc(j)), &gop(i - 2), &gop(i - 1), &gop(i), 0.80f, &(*ctours)[j][vrt * 3]); vrt += 1;
				(*ctours)[j][vrt * 3] = (float) gop(i).x / 64; (*ctours)[j][vrt * 3 + 1] = (float) gop(i).y / 64; vrt += 1;
			}
			else if((i >= 3 || (j && i - (goc(j - 1) + 1) >= 3)) && FTCT(got(i)) == FTCT_ON && FTCT(got(i - 1)) == FTCT_CONIC && FTCT(got(i - 2)) == FTCT_CONIC && FTCT(got(i - 3)) == FTCT_ON)
			{
				//vrt += 3 + 1 + 3 + 1;
				FT_Vector mdl;

				mdl.x = (FT_Pos) (gop(i - 2).x + gop(i - 1).x) / 2; mdl.y = (FT_Pos) (gop(i - 2).y + gop(i - 1).y) / 2;
				flConicBezier(&gop(i - 3), &gop(i - 2), &mdl, 0.25f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&gop(i - 3), &gop(i - 2), &mdl, 0.50f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&gop(i - 3), &gop(i - 2), &mdl, 0.75f, &(*ctours)[j][vrt * 3]); vrt += 1;
				(*ctours)[j][vrt * 3] = (float) mdl.x / 64; (*ctours)[j][vrt * 3 + 1] = (float) mdl.y / 64; vrt += 1;
				flConicBezier(&mdl, &gop(i - 1), &gop(i), 0.25f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&mdl, &gop(i - 1), &gop(i), 0.50f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flConicBezier(&mdl, &gop(i - 1), &gop(i), 0.75f, &(*ctours)[j][vrt * 3]); vrt += 1;
				(*ctours)[j][vrt * 3] = (float) gop(i).x / 64; (*ctours)[j][vrt * 3 + 1] = (float) gop(i).y / 64; vrt += 1;
			}
			else if((i >= 3 || (j && i - (goc(j - 1) + 1) >= 3)) && FTCT(got(i)) == FTCT_ON && FTCT(got(i - 1)) == FTCT_CUBIC && FTCT(got(i - 2)) == FTCT_CUBIC && FTCT(got(i - 3)) == FTCT_ON)
			{
				//vrt += 4 + 1;
				flCubicBezier(&gop(i - 3), &gop(i - 2), &gop(i - 1), &gop(i), 0.20f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flCubicBezier(&gop(i - 3), &gop(i - 2), &gop(i - 1), &gop(i), 0.40f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flCubicBezier(&gop(i - 3), &gop(i - 2), &gop(i - 1), &gop(i), 0.60f, &(*ctours)[j][vrt * 3]); vrt += 1;
				flCubicBezier(&gop(i - 3), &gop(i - 2), &gop(i - 1), &gop(i), 0.80f, &(*ctours)[j][vrt * 3]); vrt += 1;
				(*ctours)[j][vrt * 3] = (float) gop(i).x / 64; (*ctours)[j][vrt * 3 + 1] = (float) gop(i).y / 64; vrt += 1;
			}
		}
	}
}

static
void
flDrawOutlineGlyph(FT_GlyphSlot glyph)
{
	unsigned int i, j;
	unsigned int *vrts;
	GLdouble **ctours;

	flCreateContours(glyph, &vrts, &ctours);

	for(j = 0; j < (unsigned int) glyph->outline.n_contours; j++)
	{
		glBegin(GL_LINE_LOOP);
		for(i = 0; i < vrts[j]; i++)
			glVertex3dv(&ctours[j][i * 3]);
		glEnd();
	}

	free(vrts);
	for(i = 0; i < (unsigned int) glyph->outline.n_contours; i++)
		free(ctours[i]);
	free(ctours);
}

static
void
flDrawWireGlyph(FT_GlyphSlot glyph, unsigned int z)
{
	unsigned int i, j;
	unsigned int *vrts;
	GLdouble **ctours;

	flCreateContours(glyph, &vrts, &ctours);

	glPushMatrix();
	glTranslatef(0, 0, - (float) z);
	for(j = 0; j < (unsigned int) glyph->outline.n_contours; j++)
	{
		glBegin(GL_LINE_LOOP);
		for(i = 0; i < vrts[j]; i++)
			glVertex3dv(&ctours[j][i * 3]);
		glEnd();
	}
	glPopMatrix();

	for(j = 0; j < (unsigned int) glyph->outline.n_contours; j++)
	{
		glBegin(GL_LINES);
		for(i = 0; i < vrts[j]; i++)
		{
			glVertex3d(ctours[j][i * 3], ctours[j][i * 3 + 1], - (double) z);
			glVertex3d(ctours[j][i * 3], ctours[j][i * 3 + 1], 0.0);
		}
		glEnd();
	}

	for(j = 0; j < (unsigned int) glyph->outline.n_contours; j++)
	{
		glBegin(GL_LINE_LOOP);
		for(i = 0; i < vrts[j]; i++)
			glVertex3dv(&ctours[j][i * 3]);
		glEnd();
	}

	free(vrts);
	for(i = 0; i < (unsigned int) glyph->outline.n_contours; i++)
		free(ctours[i]);
	free(ctours);
}

static
void
flDrawFillGlyph(FT_GlyphSlot glyph)
{
	unsigned int i, j;
	unsigned int *vrts;
	GLdouble **ctours;
	GLUtesselator *tess;

	flCreateContours(glyph, &vrts, &ctours);

	tess = gluNewTess();
	gluTessCallback(tess, GLU_TESS_VERTEX, (_GLUfuncptr) &glVertex3dv);
	gluTessCallback(tess, GLU_TESS_BEGIN, (_GLUfuncptr) &glBegin);
	gluTessCallback(tess, GLU_TESS_END, (_GLUfuncptr) &glEnd);

	gluTessBeginPolygon(tess, NULL);
	for(j = 0; j < (unsigned int) glyph->outline.n_contours; j++)
	{
		gluTessBeginContour(tess);
		for(i = 0; i < vrts[j]; i++)
			gluTessVertex(tess, &ctours[j][i * 3], &ctours[j][i * 3]);
		gluTessEndContour(tess);

	}
	gluTessEndPolygon(tess);

	gluDeleteTess(tess);

	free(vrts);
	for(i = 0; i < (unsigned int) glyph->outline.n_contours; i++)
		free(ctours[i]);
	free(ctours);
}

static
void
flDrawSolidGlyph(FT_GlyphSlot glyph, unsigned int z)
{
	unsigned int i, j;
	unsigned int *vrts;
	GLdouble **ctours;
	GLUtesselator *tess;

	flCreateContours(glyph, &vrts, &ctours);

	tess = gluNewTess();
	gluTessCallback(tess, GLU_TESS_VERTEX, (_GLUfuncptr) &glVertex3dv);
	gluTessCallback(tess, GLU_TESS_BEGIN, (_GLUfuncptr) &glBegin);
	gluTessCallback(tess, GLU_TESS_END, (_GLUfuncptr) &glEnd);

	glPushMatrix();
	glTranslatef(0, 0, - (float) z);
	gluTessBeginPolygon(tess, NULL);
	for(j = 0; j < (unsigned int) glyph->outline.n_contours; j++)
	{
		gluTessBeginContour(tess);
		for(i = 0; i < vrts[j]; i++)
			gluTessVertex(tess, &ctours[j][i * 3], &ctours[j][i * 3]);
		gluTessEndContour(tess);

	}
	gluTessEndPolygon(tess);
	glPopMatrix();

	for(j = 0; j < (unsigned int) glyph->outline.n_contours; j++)
	{
		glBegin(GL_QUAD_STRIP);
		for(i = 0; i < vrts[j]; i++)
		{
			glVertex3d(ctours[j][i * 3], ctours[j][i * 3 + 1], - (double) z);
			glVertex3d(ctours[j][i * 3], ctours[j][i * 3 + 1], 0.0);
		}
		glVertex3d(ctours[j][0 * 3], ctours[j][0 * 3 + 1], - (double) z);
		glVertex3d(ctours[j][0 * 3], ctours[j][0 * 3 + 1], 0.0);
		glEnd();

	}

	gluTessNormal(tess, 0.0, 0.0, 1.0);
	gluTessBeginPolygon(tess, NULL);
	for(j = 0; j < (unsigned int) glyph->outline.n_contours; j++)
	{
		gluTessBeginContour(tess);
		for(i = 0; i < vrts[j]; i++)
			gluTessVertex(tess, &ctours[j][i * 3], &ctours[j][i * 3]);
		gluTessEndContour(tess);

	}
	gluTessEndPolygon(tess);

	gluDeleteTess(tess);

	free(vrts);
	for(i = 0; i < (unsigned int) glyph->outline.n_contours; i++)
		free(ctours[i]);
	free(ctours);
}

void
flFontDrawStringTexture(unsigned int id, unsigned int string)
{
	FLFont *font;
	const wchar_t *c;

	font = store[id];

	glPushMatrix();
	for(c = tlStringData(string); *c; c++)
	{
		FT_Load_Char(font->face, *c, FT_LOAD_RENDER);
		flDrawTextureGlyph(font->face->glyph);
		glTranslatef((float) font->face->glyph->advance.x / 64, (float) font->face->glyph->advance.y, 0);
	}
	glPopMatrix();
}

void
flFontDrawStringOutline(unsigned int id, unsigned int string)
{
	FLFont *font;
	const wchar_t *c;

	font = store[id];

	glPushMatrix();
	for(c = tlStringData(string); *c; c++)
	{
		FT_Load_Char(font->face, *c, FT_LOAD_DEFAULT);
		flDrawOutlineGlyph(font->face->glyph);
		glTranslatef((float) font->face->glyph->advance.x / 64, (float) font->face->glyph->advance.y, 0);
	}
	glPopMatrix();
}

void
flFontDrawStringWire(unsigned int id, unsigned int string, unsigned int z)
{
	FLFont *font;
	const wchar_t *c;

	font = store[id];

	glPushMatrix();
	for(c = tlStringData(string); *c; c++)
	{
		FT_Load_Char(font->face, *c, FT_LOAD_DEFAULT);
		flDrawWireGlyph(font->face->glyph, z);
		glTranslatef((float) font->face->glyph->advance.x / 64, (float) font->face->glyph->advance.y, 0);
	}
	glPopMatrix();
}

void
flFontDrawStringFill(unsigned int id, unsigned int string)
{
	FLFont *font;
	const wchar_t *c;

	font = store[id];

	glPushMatrix();
	for(c = tlStringData(string); *c; c++)
	{
		FT_Load_Char(font->face, *c, FT_LOAD_DEFAULT);
		flDrawFillGlyph(font->face->glyph);
		glTranslatef((float) font->face->glyph->advance.x / 64, (float) font->face->glyph->advance.y, 0);
	}
	glPopMatrix();
}

void
flFontDrawStringSolid(unsigned int id, unsigned int string, unsigned int z)
{
	FLFont *font;
	const wchar_t *c;

	font = store[id];

	glPushMatrix();
	for(c = tlStringData(string); *c; c++)
	{
		FT_Load_Char(font->face, *c, FT_LOAD_DEFAULT);
		flDrawSolidGlyph(font->face->glyph, z);
		glTranslatef((float) font->face->glyph->advance.x / 64, (float) font->face->glyph->advance.y, 0);
	}
	glPopMatrix();
}
